/*
* Purpose: kqueue api interface. 
* Created: 2012-03-05 Daly 
*/

//for freebsd 
#ifdef HAVE_KQUEUE

#include "xnt_base.h"
#include "xnt_event.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/event.h>
#include <signal.h>
#include <errno.h>

//global variables
//Warning: Should not be operated by multi-threads in this version. 
//      The event should be controlled by one thread
// The next version may be protected by mutex.

static struct kevent *_event_list = NULL;    //triggered event list
static struct kevent *_change_list = NULL;    //clear it before event poll
static unsigned int _max_changes, _nchanges;
static unsigned int _max_events;
static int _kqueue_fd = -1;  

int kq_init(xnt_conf_t* cf);
int kq_shutdown();
int kq_event_add(xnt_event_t* ev, int fd, int ev_mask);
int kq_event_del(xnt_event_t* ev, int fd, int ev_mask);
int kq_process_event(xnt_msec_t t);

static struct xnt_event_driver_t _drv_kqueue = {
    "KQUEUE",
	kq_init,
	kq_shutdown,
	kq_event_add,
	kq_event_del,
	kq_process_event,
};

//global driver define
xnt_event_driver_t *g_event_drv = &_drv_kqueue;

int kq_init(xnt_conf_t* cf)
{
	int max_fd = cf->max_fd;
	if (_kqueue_fd > 0) return XNT_RET_OK;
	_kqueue_fd = kqueue();
	if (_kqueue_fd < 0) return XNT_RET_ERR;
	_max_events = max_fd * 2;
	_max_changes = max_fd * 2;
	_event_list = (struct kevent*)malloc(sizeof(_event_list) * (_max_events + 1));
	_change_list = (struct kevent*)malloc(sizeof(_change_list) * (_max_changes + 1));
	return XNT_RET_OK;
}

int kq_shutdown()
{
	free(_event_list);
	_event_list = NULL;
	free(_change_list);
	_change_list = NULL;
	_kqueue_fd = -1;
	return 0;
}

int kq_event_add(xnt_event_t* ev, int fd, int ev_mask)
{
	struct kevent *kev;
	short filter;
	
	assert(_change_list != NULL && _kqueue_fd > 0);
	if (_nchanges >= _max_changes) {
		return XNT_RET_FULL;
	}
	
	if (ev_mask == XNT_EV_READ) filter = EVFILT_READ;
	else if (ev_mask == XNT_EV_WRITE) filter = EVFILT_WRITE;
	else return XNT_RET_PARAM_INVALID ;  //param error
	
	//append to change_list 
	//No kevent call one by one, instead, pass changelist to kevent before event_wait.
	
	kev = &_change_list[_nchanges];   
	kev->ident = fd;
	kev->filter = filter;
	kev->flags = EV_ADD | EV_ENABLE;
	kev->udata = ev;
	ev->index = _nchanges;
	ev->mask = ev_mask;
	ev->fd = fd;
	_nchanges++;
	return XNT_RET_OK;
}

int kq_event_del(xnt_event_t* ev, int fd, int ev_mask)
{
	xnt_event_t *ev_tail;
	struct kevent *kev;
	int filter;
	
	assert(_nchanges < _max_changes && _kqueue_fd > 0);
	if (ev->index < _nchanges && ((xnt_event_t*)(_change_list[ev->index].udata) == ev) )
	{
		//the event still not pass to kevent kernel. 
		//delete it from changelist and swap the last event to this slot.
		_nchanges--;
		ev_tail = (xnt_event_t*)_change_list[_nchanges].udata;
		_change_list[ev->index] = _change_list[_nchanges];   //replaced by the last changelist item
		ev_tail->index = ev->index;
		return XNT_RET_OK;
	}
	
	if (ev_mask == XNT_EV_READ) filter = EVFILT_READ;
	else if (ev_mask == XNT_EV_WRITE) filter = EVFILT_WRITE;
	else return XNT_RET_PARAM_INVALID ;  //param error
	
	kev = &_change_list[_nchanges];
	kev->ident = fd;
	kev->filter = filter;
	kev->flags = EV_DELETE;
	ev->index = _nchanges;
	ev->mask = 0;
	ev->fd = -1;
	_nchanges++;
	
}

//param: 
//  t  --  timeout. (millisecond or XNT_TIMER_INFINITY)
int kq_process_event(xnt_msec_t t)
{
	int n_pass, i, fd;
	int events;
	struct timespec *tp = NULL;
	struct timespec ts;
	struct xnt_event_t *ev;
	struct kevent *kev;
	
	//flush changelist
	n_pass = _nchanges;
	_nchanges = 0;
	
	if (t > 0) {
		ts.tv_sec = t / 1000;
		ts.tv_nsec = (t % 1000) * 1000000;
		tp = &ts;
	}
	events = kevent(_kqueue_fd, _change_list, n_pass, _event_list, _max_events, tp);
	if (events < 0) {
		if (errno == EINTR) return 0;
		//not interrupt
		return XNT_RET_ERR;
	}
	for (i = 0; i < events; i++) {
		kev = &_event_list[i];
		fd = kev->ident;
		//caller must make sure that ev is available 
		ev = (xnt_event_t*)kev->udata;
		if (ev->handler) ev->handler(ev, fd);
	}
	return events;
}

#endif  // XNT_USE_KQUEUE
